﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Drawing;

namespace ProCP.Sim
{
    [Serializable()]
    class Simulation
    {
        //ToDrawy
        Size dim; //size of the map
        Size scalar;
        Rectangle[] rGrid;// rectangles around the crossings
        [NonSerialized()]
        Pen pContours = new Pen(Color.Black,1); //Draw rectangles
        [NonSerialized()]
        Pen sCountor = new Pen(Color.Red, 5);   //Draw selected rectangle
        public Boolean isSaveRequired = false;
        int test1;//for generating destinations toDo Generate Destinations
        int test2;

        //Logic
        public Cross[] cMap { get; set; }
        List<int[]> destinations = new List<int[]>();

        //timer __change to event driven later
        DateTime timer;

        public Simulation(Size dimensions, Point corner, Size scalar)
        {
            //isSaveRequired = true;
            dim = dimensions;
            this.scalar = scalar;
            rGrid = new Rectangle[dim.Width*dim.Height];
            
            for (int iY = 0; iY < dim.Height; iY++) 
            {
                for (int iX = 0; iX < dim.Width; iX++)
                {
                    rGrid[iY*dim.Width+iX]=new Rectangle(
                        corner.X+ iX* scalar.Width-Convert.ToInt32( pContours.Width),
                         corner.Y + iY * scalar.Height - Convert.ToInt32(pContours.Width),
                        scalar.Width + Convert.ToInt32(pContours.Width),
                         scalar.Height + Convert.ToInt32(pContours.Width));
                }
            }

            cMap = new Cross[dimensions.Width*dimensions.Height];
            //testing
            timer = DateTime.Now;
        }

        public void AddCrossing(int location, int type) 
        {
            isSaveRequired = true;
            cMap[location] = new Cross(
                rGrid[location].Location, scalar, type);
            
        }

        public void RemoveCrossing(int location)
        {
            //isSaveRequired = true;
            cMap[location] = null;
           
        }

        public void RunSimaulation()
        {
            isSaveRequired = true;
            UpdateTrafficLights();
            GenerateCars();
            UpdateCars();
        }

        public void StopSimulation()
        {
            for (int i = 0; i < cMap.Length; i++)
            {
                if (cMap[i] != null)
                {
                    cMap[i].RemoveAllCars();
                }
            }
        }

        private void UpdateTrafficLights()
        {
            isSaveRequired = true;
            foreach (Cross c in cMap)
            {
                if (c != null)
                {
                    c.SimluateLights();
                }
            }
        }

        private void UpdateCars()
        {//sorry for very nested loop :D
            // for each crossing >> for each road>> for each lane>> foreach car :)
            //add multithreading per crossing.. paralelfor
            isSaveRequired = true;
            Parallel.For(0, cMap.Length, new ParallelOptions{MaxDegreeOfParallelism = 2},  iC =>
            {
                if (cMap[iC] != null) //crossing
                {
                    for (var iR = 0; iR < 4; iR++) //roads
                    {
                        for (var iLaneIn = 0; iLaneIn < cMap[iC].roads[iR].lanesIn.Length; iLaneIn++) //laneIn
                        {
                            if (cMap[iC].roads[iR].lanesIn[iLaneIn].MoveCars())
                            {
                                Car c = cMap[iC].roads[iR].lanesIn[iLaneIn].carsOnLane.First();
                                if (cMap[iC].roads[iR].lanesIn[iLaneIn].TrafficLightValue != 0 && !cMap[iC].InterestsWithCarsInCross(c.ToDraw))
                                {
                                    MoveFromLaneInToLaneOut(carLocation: new[] {iC, iR, iLaneIn});
                                    cMap[iC].carsInCrossing.Add(c);
                                }

                            }
                        }
                        for (var iLaneOut = 0; iLaneOut < cMap[iC].roads[iR].lanesOut.Length; iLaneOut++)
                        {
                            if (cMap[iC].roads[iR].lanesOut[iLaneOut].MoveCars())
                            {
                                MoveFromLaneOutToLaneIn(carLocation: new[] {iC, iR, iLaneOut});
                            }
                        }
                    }
                }
            });
        }

        public int MouseClickToPossition(Point point) 
        {
            for (int i = 0; i < rGrid.Length; i++)
            {
                if (rGrid[i].Contains(point)) {return i;}  
            }
            return -1;
        }

        private void GenerateCars()
        {
            
            isSaveRequired = true;
            if (destinations.Count > 0)
            {
                cMap[0].roads[0].lanesIn[0].AddCar(destinations[0]);
                destinations.RemoveAt(0);
                
                cMap[0].roads[3].lanesIn[0].AddCar(destinations[0]);
                destinations.RemoveAt(0);
                
            }
            else if(DateTime.Now>timer)
            {
                timer = DateTime.Now.AddSeconds(0.125);
                GenerateDestinations();
            }
        }
        private void GenerateDestinations()
        {
            //TO DO: Uncomment only for testing

            /*for (int i = 0; i < cMap.Count(); ++i)
            {
                if (i / 4 == 0)
                {
                    cMap[i].roads[0].trafficIn = 3;
                    cMap[i].roads[0].trafficOut = 100 / 10;
                    //cMap[i].roads[0].trafficOut = 100 / 14;
                }

                if (i % 4 == 3)
                {
                    cMap[i].roads[1].trafficIn = 3;
                    cMap[i].roads[1].trafficOut = 100 / 10;
                    //cMap[i].roads[1].trafficOut = 100 / 14;
                }

                if (i / 4 == 2)
                {
                    cMap[i].roads[2].trafficIn = 3;
                    //cMap[i].roads[2].trafficOut = 100 / 14;
                }

                if (i % 4 == 0)
                {
                    cMap[i].roads[3].trafficIn = 3;
                    cMap[i].roads[3].trafficOut = 100 / 10;
                    //cMap[i].roads[3].trafficOut = 100 / 14;
                }
            }*/


            isSaveRequired = true;

            int TotalOfCars = 0;
            List<int[]> IndexOfLane = new List<int[]>(0);
            List<int> PercentageOfCars = new List<int>(0);

            // run every crossing, then every road, then every lane
            for (int i = 0; i < cMap.Count(); ++i)
            {
                if (cMap[i] != null)
                {
                    for (int j = 0; j < cMap[i].roads.Count(); ++j)
                    {
                        //check for entrances, for each entrance add the number of cars coming in to the total of cars coming in this round
                        if (cMap[j] != null)
                        {
                            TotalOfCars += cMap[i].roads[j].trafficIn;
                            Console.WriteLine(TotalOfCars);
                            //check for exits, for each exit add the percentage of cars going in that exit
                            if (cMap[i].roads[j].trafficOut > 0)
                            {
                                IndexOfLane.Add(new int[] { i, j });
                                PercentageOfCars.Add((int)cMap[i].roads[j].trafficOut);
                            }
                        }
                    }
                }
            }

            List<int> NumberOfCarsLeavingInThisExit = new List<int>(IndexOfLane.Count);
            int NumberOfOddsLeft = 0;

            for (int i = 0; i < IndexOfLane.Count; ++i)
            {
                NumberOfCarsLeavingInThisExit.Add(TotalOfCars * PercentageOfCars[i] / 100);
                PercentageOfCars[i] = (TotalOfCars * PercentageOfCars[i]) % 100;
                NumberOfOddsLeft += PercentageOfCars[i];
            }

            int NumberOfCarsLeft = NumberOfOddsLeft / 100;

            while (NumberOfCarsLeft > 0)
            {
                Random rnd = new Random();
                int NextDestination = rnd.Next(1, NumberOfOddsLeft);

                int index = -1;
                int counter = 0;

                while (NextDestination > counter)
                {
                    index++;
                    counter += PercentageOfCars[index];
                }

                NumberOfCarsLeft--;
                NumberOfOddsLeft -= PercentageOfCars[index];
                NumberOfCarsLeavingInThisExit[index]++;
                PercentageOfCars[index] = 0;
            }

            for (int i = 0; i < IndexOfLane.Count; ++i)
            {
                NumberOfCarsLeavingInThisExit.RemoveAt(i);
                IndexOfLane.RemoveAt(i);
            }

            while (IndexOfLane.Count > 0)
            {
                int NextDestination;

                Random rnd = new Random();
                NextDestination = rnd.Next(0, IndexOfLane.Count);

                destinations.Add(IndexOfLane[NextDestination]);
                NumberOfCarsLeavingInThisExit[NextDestination]--;

                if (NumberOfCarsLeavingInThisExit[NextDestination] == 0)
                {
                    NumberOfCarsLeavingInThisExit.RemoveAt(NextDestination);
                    IndexOfLane.RemoveAt(NextDestination);
                }
            }
        }

        public void SetTrafficLight(int location, int greenTime11, int greenTime12, int greenTime21, int greenTime22)
        {
            isSaveRequired = true;
            //simplify later..
            switch (GetCrossType(location))
            {
                case 1:
                    cMap[location].roads[0].lanesIn[1].GreenTime = greenTime11;
                    cMap[location].roads[0].lanesIn[0].GreenTime = greenTime12;
                    cMap[location].roads[2].lanesIn[1].GreenTime = greenTime11;
                    cMap[location].roads[2].lanesIn[0].GreenTime = greenTime12;

                    cMap[location].roads[1].lanesIn[1].GreenTime = greenTime21;
                    cMap[location].roads[1].lanesIn[0].GreenTime = greenTime22;
                    cMap[location].roads[3].lanesIn[1].GreenTime = greenTime21;
                    cMap[location].roads[3].lanesIn[0].GreenTime = greenTime22;
                    break;
                case 2:

                    break;
            }
        }

        public bool IsEmpty(int location)
        {
            if (location>=0 && cMap[location] == null)
            {
                return true;
            }
            return false;
        }

        public int GetCrossType(int location)
        {
            return cMap[location].CType;
        }

        public void DrawSelectedRectangle(Graphics g, int recId)
        {
            isSaveRequired = true;
            g.DrawRectangle(sCountor, rGrid[recId]);
        }

        public void Draw(Graphics g)
        {
           // isSaveRequired = true;
            g.DrawRectangles(pContours,rGrid);
            for(int i =0;i<cMap.Length; i++)
            {
                if (cMap[i] != null)
                {
                    cMap[i].Draw(g);
                }
            };

            for (int i = 0; i < cMap.Length; i++)
            {
                if (cMap[i] != null)
                {
                    cMap[i].DrawCars(g);
                }
            };
            
        }

        private int FindDirection(Car c, int[] carLocation)
        {          //direction where car needs to go
            //toDo find possible route
            Point direction = new Point(x: c.destination[0] % dim.Width - carLocation[0] % dim.Width, y: ((c.destination[0] / dim.Width) - (carLocation[0]) / dim.Width));

            //cross 4 poss
            int r;//which road whould we go
            if (direction.Y < 0)
            {
                r = 0;
            }
            else if (direction.X > 0)
            {
                r = 1;
            }
            else if (direction.Y > 0)
            {
                r = 2;
            }
            else if (direction.X < 0)
            {
                r = 3;
            }
            else
            {
                r = c.destination[1];// if destination in the same crossing
            }
            return r;

        }

        //checks for shortest path and update MoveTo point in car
        //CarLocation[0]- crossing,[1]-road, [2]- line
        private void MoveFromLaneInToLaneOut(int[] carLocation) //between crossings
        {
            Car c = cMap[carLocation[0]].roads[carLocation[1]].lanesIn[carLocation[2]].carsOnLane.First();

            int r = FindDirection(c, carLocation);

            if (cMap[carLocation[0]].roads[r].lanesOut[0].AddCar(c))
            {
                if (cMap[carLocation[0]].roads[r].lanesOut[0].carsOnLane.Count > 0)
                {
                    cMap[carLocation[0]].roads[r].lanesOut[0].carsOnLane.Last().stage = 0;


                    cMap[carLocation[0]].roads[carLocation[1]].lanesIn[carLocation[2]].carsOnLane.RemoveAt(0);
                }
            }
            
            
            
        }


        private void MoveFromLaneOutToLaneIn(int[] carLocation) 
        {
            Car c = cMap[carLocation[0]].roads[carLocation[1]].lanesOut[carLocation[2]].carsOnLane.First();
            if (c.stage == 1)
            {
                cMap[carLocation[0]].carsInCrossing.Remove(c);
            }
            if (c.destination[0] != carLocation[0])
            {
                var newCarLocation = new int[3];
                newCarLocation[1] = (carLocation[1] + 2)%4;

                switch (newCarLocation[1])
                {
                    case 0:
                        newCarLocation[0] = carLocation[0] + dim.Width;
                        break;
                    case 1:
                        newCarLocation[0] = carLocation[0] - 1;
                        break;
                    case 2:
                        newCarLocation[0] = carLocation[0] - dim.Width;
                        break;
                    case 3:
                        newCarLocation[0] = carLocation[0] + 1;
                        break;
                }
                int r = FindDirection(c, newCarLocation);

                switch ((r - newCarLocation[1] + 4)%4)
                {
                    case 1:
                        newCarLocation[2] = 0;
                        break;
                    case 2:
                        if (cMap[newCarLocation[0]].roads[newCarLocation[1]].lanesIn.Length == 1)
                        {
                            newCarLocation[2] = 0;
                        }
                        else
                        {
                            newCarLocation[2] = 1;
                        }
                        break;
                    case 3:
                        if (cMap[newCarLocation[0]].roads[newCarLocation[1]].lanesIn.Length == 1)
                        {
                            newCarLocation[2] = 0;
                        }
                        else
                        {
                            newCarLocation[2] = 1;
                        }
                        break;
                }
                
                if (cMap[newCarLocation[0]]!=null&&cMap[newCarLocation[0]].roads[newCarLocation[1]].lanesIn[newCarLocation[2]].AddCar(c))
                {

                    if (cMap[newCarLocation[0]].roads[newCarLocation[1]].lanesIn[newCarLocation[2]].carsOnLane.Last() != null)
                    {
                    cMap[newCarLocation[0]].roads[newCarLocation[1]].lanesIn[newCarLocation[2]].carsOnLane.Last().stage
                         = 0;
                    
                    cMap[carLocation[0]].roads[carLocation[1]].lanesOut[carLocation[2]].carsOnLane.RemoveAt(0);
                }
                }
            }
            else
            {
                cMap[carLocation[0]].roads[carLocation[1]].lanesOut[carLocation[2]].carsOnLane.RemoveAt(0);

            }
        }
       
        public int Checkneighbors(int location)
        {
            int result = 0;
            List<int> listOfCrossings = new List<int>();
            for (int i = 0; i < cMap.Length; i++)
            {
                if (!IsEmpty(i))
                {
                    listOfCrossings.Add(i);
                }
            }
            if (listOfCrossings.Contains(location - 4))
            {
                result += 1;
            }
            if (listOfCrossings.Contains(location + 1) && location != 3 && location != 7)
            {
                result += 10;
            }
            if (listOfCrossings.Contains(location + 4))
            {
                result += 100;
            }
            if (listOfCrossings.Contains(location - 1) && location != 4 && location != 8)
            {
                result += 1000;
            }
            return result;
        }
    }
}
